/**
 * author: Di (微信小程序开发工程师)
 * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
 *               垂直微信小程序开发交流社区
 *
 * github地址: https://github.com/icindy/wxParse
 *
 * for: 微信小程序富文本解析
 * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
 */

function getDefaultOpts(simple) {
	'use strict';

	var defaultOptions = {
		omitExtraWLInCodeBlocks: {
			defaultValue: false,
			describe: 'Omit the default extra whiteline added to code blocks',
			type: 'boolean'
		},
		noHeaderId: {
			defaultValue: false,
			describe: 'Turn on/off generated header id',
			type: 'boolean'
		},
		prefixHeaderId: {
			defaultValue: false,
			describe: 'Specify a prefix to generated header ids',
			type: 'string'
		},
		headerLevelStart: {
			defaultValue: false,
			describe: 'The header blocks level start',
			type: 'integer'
		},
		parseImgDimensions: {
			defaultValue: false,
			describe: 'Turn on/off image dimension parsing',
			type: 'boolean'
		},
		simplifiedAutoLink: {
			defaultValue: false,
			describe: 'Turn on/off GFM autolink style',
			type: 'boolean'
		},
		literalMidWordUnderscores: {
			defaultValue: false,
			describe: 'Parse midword underscores as literal underscores',
			type: 'boolean'
		},
		strikethrough: {
			defaultValue: false,
			describe: 'Turn on/off strikethrough support',
			type: 'boolean'
		},
		tables: {
			defaultValue: false,
			describe: 'Turn on/off tables support',
			type: 'boolean'
		},
		tablesHeaderId: {
			defaultValue: false,
			describe: 'Add an id to table headers',
			type: 'boolean'
		},
		ghCodeBlocks: {
			defaultValue: true,
			describe: 'Turn on/off GFM fenced code blocks support',
			type: 'boolean'
		},
		tasklists: {
			defaultValue: false,
			describe: 'Turn on/off GFM tasklist support',
			type: 'boolean'
		},
		smoothLivePreview: {
			defaultValue: false,
			describe: 'Prevents weird effects in live previews due to incomplete input',
			type: 'boolean'
		},
		smartIndentationFix: {
			defaultValue: false,
			description: 'Tries to smartly fix identation in es6 strings',
			type: 'boolean'
		}
	};
	if (simple === false) {
		return JSON.parse(JSON.stringify(defaultOptions));
	}
	var ret = {};
	for (var opt in defaultOptions) {
		if (defaultOptions.hasOwnProperty(opt)) {
			ret[opt] = defaultOptions[opt].defaultValue;
		}
	}
	return ret;
}

/**
 * Created by Tivie on 06-01-2015.
 */

// Private properties
var showdown = {},
	parsers = {},
	extensions = {},
	globalOptions = getDefaultOpts(true),
	flavor = {
		github: {
			omitExtraWLInCodeBlocks: true,
			prefixHeaderId: 'user-content-',
			simplifiedAutoLink: true,
			literalMidWordUnderscores: true,
			strikethrough: true,
			tables: true,
			tablesHeaderId: true,
			ghCodeBlocks: true,
			tasklists: true
		},
		vanilla: getDefaultOpts(true)
	};

/**
 * helper namespace
 * @type {{}}
 */
showdown.helper = {};

/**
 * TODO LEGACY SUPPORT CODE
 * @type {{}}
 */
showdown.extensions = {};

/**
 * Set a global option
 * @static
 * @param {string} key
 * @param {*} value
 * @returns {showdown}
 */
showdown.setOption = function (key, value) {
	'use strict';
	globalOptions[key] = value;
	return this;
};

/**
 * Get a global option
 * @static
 * @param {string} key
 * @returns {*}
 */
showdown.getOption = function (key) {
	'use strict';
	return globalOptions[key];
};

/**
 * Get the global options
 * @static
 * @returns {{}}
 */
showdown.getOptions = function () {
	'use strict';
	return globalOptions;
};

/**
 * Reset global options to the default values
 * @static
 */
showdown.resetOptions = function () {
	'use strict';
	globalOptions = getDefaultOpts(true);
};

/**
 * Set the flavor showdown should use as default
 * @param {string} name
 */
showdown.setFlavor = function (name) {
	'use strict';
	if (flavor.hasOwnProperty(name)) {
		var preset = flavor[name];
		for (var option in preset) {
			if (preset.hasOwnProperty(option)) {
				globalOptions[option] = preset[option];
			}
		}
	}
};

/**
 * Get the default options
 * @static
 * @param {boolean} [simple=true]
 * @returns {{}}
 */
showdown.getDefaultOptions = function (simple) {
	'use strict';
	return getDefaultOpts(simple);
};

/**
 * Get or set a subParser
 *
 * subParser(name)       - Get a registered subParser
 * subParser(name, func) - Register a subParser
 * @static
 * @param {string} name
 * @param {function} [func]
 * @returns {*}
 */
showdown.subParser = function (name, func) {
	'use strict';
	if (showdown.helper.isString(name)) {
		if (typeof func !== 'undefined') {
			parsers[name] = func;
		} else {
			if (parsers.hasOwnProperty(name)) {
				return parsers[name];
			} else {
				throw Error('SubParser named ' + name + ' not registered!');
			}
		}
	}
};

/**
 * Gets or registers an extension
 * @static
 * @param {string} name
 * @param {object|function=} ext
 * @returns {*}
 */
showdown.extension = function (name, ext) {
	'use strict';

	if (!showdown.helper.isString(name)) {
		throw Error('Extension \'name\' must be a string');
	}

	name = showdown.helper.stdExtName(name);

	// Getter
	if (showdown.helper.isUndefined(ext)) {
		if (!extensions.hasOwnProperty(name)) {
			throw Error('Extension named ' + name + ' is not registered!');
		}
		return extensions[name];

		// Setter
	} else {
		// Expand extension if it's wrapped in a function
		if (typeof ext === 'function') {
			ext = ext();
		}

		// Ensure extension is an array
		if (!showdown.helper.isArray(ext)) {
			ext = [ext];
		}

		var validExtension = validate(ext, name);

		if (validExtension.valid) {
			extensions[name] = ext;
		} else {
			throw Error(validExtension.error);
		}
	}
};

/**
 * Gets all extensions registered
 * @returns {{}}
 */
showdown.getAllExtensions = function () {
	'use strict';
	return extensions;
};

/**
 * Remove an extension
 * @param {string} name
 */
showdown.removeExtension = function (name) {
	'use strict';
	delete extensions[name];
};

/**
 * Removes all extensions
 */
showdown.resetExtensions = function () {
	'use strict';
	extensions = {};
};

/**
 * Validate extension
 * @param {array} extension
 * @param {string} name
 * @returns {{valid: boolean, error: string}}
 */
function validate(extension, name) {
	'use strict';

	var errMsg = (name) ? 'Error in ' + name + ' extension->' : 'Error in unnamed extension',
		ret = {
			valid: true,
			error: ''
		};

	if (!showdown.helper.isArray(extension)) {
		extension = [extension];
	}

	for (var i = 0; i < extension.length; ++i) {
		var baseMsg = errMsg + ' sub-extension ' + i + ': ',
			ext = extension[i];
		if (typeof ext !== 'object') {
			ret.valid = false;
			ret.error = baseMsg + 'must be an object, but ' + typeof ext + ' given';
			return ret;
		}

		if (!showdown.helper.isString(ext.type)) {
			ret.valid = false;
			ret.error = baseMsg + 'property "type" must be a string, but ' + typeof ext.type + ' given';
			return ret;
		}

		var type = ext.type = ext.type.toLowerCase();

		// normalize extension type
		if (type === 'language') {
			type = ext.type = 'lang';
		}

		if (type === 'html') {
			type = ext.type = 'output';
		}

		if (type !== 'lang' && type !== 'output' && type !== 'listener') {
			ret.valid = false;
			ret.error = baseMsg + 'type ' + type + ' is not recognized. Valid values: "lang/language", "output/html" or "listener"';
			return ret;
		}

		if (type === 'listener') {
			if (showdown.helper.isUndefined(ext.listeners)) {
				ret.valid = false;
				ret.error = baseMsg + '. Extensions of type "listener" must have a property called "listeners"';
				return ret;
			}
		} else {
			if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) {
				ret.valid = false;
				ret.error = baseMsg + type + ' extensions must define either a "regex" property or a "filter" method';
				return ret;
			}
		}

		if (ext.listeners) {
			if (typeof ext.listeners !== 'object') {
				ret.valid = false;
				ret.error = baseMsg + '"listeners" property must be an object but ' + typeof ext.listeners + ' given';
				return ret;
			}
			for (var ln in ext.listeners) {
				if (ext.listeners.hasOwnProperty(ln)) {
					if (typeof ext.listeners[ln] !== 'function') {
						ret.valid = false;
						ret.error = baseMsg + '"listeners" property must be an hash of [event name]: [callback]. listeners.' + ln +
							' must be a function but ' + typeof ext.listeners[ln] + ' given';
						return ret;
					}
				}
			}
		}

		if (ext.filter) {
			if (typeof ext.filter !== 'function') {
				ret.valid = false;
				ret.error = baseMsg + '"filter" must be a function, but ' + typeof ext.filter + ' given';
				return ret;
			}
		} else if (ext.regex) {
			if (showdown.helper.isString(ext.regex)) {
				ext.regex = new RegExp(ext.regex, 'g');
			}
			if (!ext.regex instanceof RegExp) {
				ret.valid = false;
				ret.error = baseMsg + '"regex" property must either be a string or a RegExp object, but ' + typeof ext.regex + ' given';
				return ret;
			}
			if (showdown.helper.isUndefined(ext.replace)) {
				ret.valid = false;
				ret.error = baseMsg + '"regex" extensions must implement a replace string or function';
				return ret;
			}
		}
	}
	return ret;
}

/**
 * Validate extension
 * @param {object} ext
 * @returns {boolean}
 */
showdown.validateExtension = function (ext) {
	'use strict';

	var validateExtension = validate(ext, null);
	if (!validateExtension.valid) {
		console.warn(validateExtension.error);
		return false;
	}
	return true;
};

/**
 * showdownjs helper functions
 */

if (!showdown.hasOwnProperty('helper')) {
	showdown.helper = {};
}

/**
 * Check if var is string
 * @static
 * @param {string} a
 * @returns {boolean}
 */
showdown.helper.isString = function isString(a) {
	'use strict';
	return (typeof a === 'string' || a instanceof String);
};

/**
 * Check if var is a function
 * @static
 * @param {string} a
 * @returns {boolean}
 */
showdown.helper.isFunction = function isFunction(a) {
	'use strict';
	var getType = {};
	return a && getType.toString.call(a) === '[object Function]';
};

/**
 * ForEach helper function
 * @static
 * @param {*} obj
 * @param {function} callback
 */
showdown.helper.forEach = function forEach(obj, callback) {
	'use strict';
	if (typeof obj.forEach === 'function') {
		obj.forEach(callback);
	} else {
		for (var i = 0; i < obj.length; i++) {
			callback(obj[i], i, obj);
		}
	}
};

/**
 * isArray helper function
 * @static
 * @param {*} a
 * @returns {boolean}
 */
showdown.helper.isArray = function isArray(a) {
	'use strict';
	return a.constructor === Array;
};

/**
 * Check if value is undefined
 * @static
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
 */
showdown.helper.isUndefined = function isUndefined(value) {
	'use strict';
	return typeof value === 'undefined';
};

/**
 * Standardidize extension name
 * @static
 * @param {string} s extension name
 * @returns {string}
 */
showdown.helper.stdExtName = function (s) {
	'use strict';
	return s.replace(/[_-]||\s/g, '').toLowerCase();
};

function escapeCharactersCallback(wholeMatch, m1) {
	'use strict';
	var charCodeToEscape = m1.charCodeAt(0);
	return '~E' + charCodeToEscape + 'E';
}

/**
 * Callback used to escape characters when passing through String.replace
 * @static
 * @param {string} wholeMatch
 * @param {string} m1
 * @returns {string}
 */
showdown.helper.escapeCharactersCallback = escapeCharactersCallback;

/**
 * Escape characters in a string
 * @static
 * @param {string} text
 * @param {string} charsToEscape
 * @param {boolean} afterBackslash
 * @returns {XML|string|void|*}
 */
showdown.helper.escapeCharacters = function escapeCharacters(text, charsToEscape, afterBackslash) {
	'use strict';
	// First we have to escape the escape characters so that
	// we can build a character class out of them
	var regexString = '([' + charsToEscape.replace(/([\[\]\\])/g, '\\$1') + '])';

	if (afterBackslash) {
		regexString = '\\\\' + regexString;
	}

	var regex = new RegExp(regexString, 'g');
	text = text.replace(regex, escapeCharactersCallback);

	return text;
};

var rgxFindMatchPos = function (str, left, right, flags) {
	'use strict';
	var f = flags || '',
		g = f.indexOf('g') > -1,
		x = new RegExp(left + '|' + right, 'g' + f.replace(/g/g, '')),
		l = new RegExp(left, f.replace(/g/g, '')),
		pos = [],
		t, s, m, start, end;

	do {
		t = 0;
		while ((m = x.exec(str))) {
			if (l.test(m[0])) {
				if (!(t++)) {
					s = x.lastIndex;
					start = s - m[0].length;
				}
			} else if (t) {
				if (!--t) {
					end = m.index + m[0].length;
					var obj = {
						left: {start: start, end: s},
						match: {start: s, end: m.index},
						right: {start: m.index, end: end},
						wholeMatch: {start: start, end: end}
					};
					pos.push(obj);
					if (!g) {
						return pos;
					}
				}
			}
		}
	} while (t && (x.lastIndex = s));

	return pos;
};

/**
 * matchRecursiveRegExp
 *
 * (c) 2007 Steven Levithan <stevenlevithan.com>
 * MIT License
 *
 * Accepts a string to search, a left and right format delimiter
 * as regex patterns, and optional regex flags. Returns an array
 * of matches, allowing nested instances of left/right delimiters.
 * Use the "g" flag to return all matches, otherwise only the
 * first is returned. Be careful to ensure that the left and
 * right format delimiters produce mutually exclusive matches.
 * Backreferences are not supported within the right delimiter
 * due to how it is internally combined with the left delimiter.
 * When matching strings whose format delimiters are unbalanced
 * to the left or right, the output is intentionally as a
 * conventional regex library with recursion support would
 * produce, e.g. "<<x>" and "<x>>" both produce ["x"] when using
 * "<" and ">" as the delimiters (both strings contain a single,
 * balanced instance of "<x>").
 *
 * examples:
 * matchRecursiveRegExp("test", "\\(", "\\)")
 * returns: []
 * matchRecursiveRegExp("<t<<e>><s>>t<>", "<", ">", "g")
 * returns: ["t<<e>><s>", ""]
 * matchRecursiveRegExp("<div id=\"x\">test</div>", "<div\\b[^>]*>", "</div>", "gi")
 * returns: ["test"]
 */
showdown.helper.matchRecursiveRegExp = function (str, left, right, flags) {
	'use strict';

	var matchPos = rgxFindMatchPos(str, left, right, flags),
		results = [];

	for (var i = 0; i < matchPos.length; ++i) {
		results.push([
			str.slice(matchPos[i].wholeMatch.start, matchPos[i].wholeMatch.end),
			str.slice(matchPos[i].match.start, matchPos[i].match.end),
			str.slice(matchPos[i].left.start, matchPos[i].left.end),
			str.slice(matchPos[i].right.start, matchPos[i].right.end)
		]);
	}
	return results;
};

/**
 *
 * @param {string} str
 * @param {string|function} replacement
 * @param {string} left
 * @param {string} right
 * @param {string} flags
 * @returns {string}
 */
showdown.helper.replaceRecursiveRegExp = function (str, replacement, left, right, flags) {
	'use strict';

	if (!showdown.helper.isFunction(replacement)) {
		var repStr = replacement;
		replacement = function () {
			return repStr;
		};
	}

	var matchPos = rgxFindMatchPos(str, left, right, flags),
		finalStr = str,
		lng = matchPos.length;

	if (lng > 0) {
		var bits = [];
		if (matchPos[0].wholeMatch.start !== 0) {
			bits.push(str.slice(0, matchPos[0].wholeMatch.start));
		}
		for (var i = 0; i < lng; ++i) {
			bits.push(
				replacement(
					str.slice(matchPos[i].wholeMatch.start, matchPos[i].wholeMatch.end),
					str.slice(matchPos[i].match.start, matchPos[i].match.end),
					str.slice(matchPos[i].left.start, matchPos[i].left.end),
					str.slice(matchPos[i].right.start, matchPos[i].right.end)
				)
			);
			if (i < lng - 1) {
				bits.push(str.slice(matchPos[i].wholeMatch.end, matchPos[i + 1].wholeMatch.start));
			}
		}
		if (matchPos[lng - 1].wholeMatch.end < str.length) {
			bits.push(str.slice(matchPos[lng - 1].wholeMatch.end));
		}
		finalStr = bits.join('');
	}
	return finalStr;
};

/**
 * POLYFILLS
 */
if (showdown.helper.isUndefined(console)) {
	console = {
		warn: function (msg) {
			'use strict';
			alert(msg);
		},
		log: function (msg) {
			'use strict';
			alert(msg);
		},
		error: function (msg) {
			'use strict';
			throw msg;
		}
	};
}

/**
 * Created by Estevao on 31-05-2015.
 */

/**
 * Showdown Converter class
 * @class
 * @param {object} [converterOptions]
 * @returns {Converter}
 */
showdown.Converter = function (converterOptions) {
	'use strict';

	var
		/**
		 * Options used by this converter
		 * @private
		 * @type {{}}
		 */
		options = {},

		/**
		 * Language extensions used by this converter
		 * @private
		 * @type {Array}
		 */
		langExtensions = [],

		/**
		 * Output modifiers extensions used by this converter
		 * @private
		 * @type {Array}
		 */
		outputModifiers = [],

		/**
		 * Event listeners
		 * @private
		 * @type {{}}
		 */
		listeners = {};

	_constructor();

	/**
	 * Converter constructor
	 * @private
	 */
	function _constructor() {
		converterOptions = converterOptions || {};

		for (var gOpt in globalOptions) {
			if (globalOptions.hasOwnProperty(gOpt)) {
				options[gOpt] = globalOptions[gOpt];
			}
		}

		// Merge options
		if (typeof converterOptions === 'object') {
			for (var opt in converterOptions) {
				if (converterOptions.hasOwnProperty(opt)) {
					options[opt] = converterOptions[opt];
				}
			}
		} else {
			throw Error('Converter expects the passed parameter to be an object, but ' + typeof converterOptions +
				' was passed instead.');
		}

		if (options.extensions) {
			showdown.helper.forEach(options.extensions, _parseExtension);
		}
	}

	/**
	 * Parse extension
	 * @param {*} ext
	 * @param {string} [name='']
	 * @private
	 */
	function _parseExtension(ext, name) {

		name = name || null;
		// If it's a string, the extension was previously loaded
		if (showdown.helper.isString(ext)) {
			ext = showdown.helper.stdExtName(ext);
			name = ext;

			// LEGACY_SUPPORT CODE
			if (showdown.extensions[ext]) {
				console.warn('DEPRECATION WARNING: ' + ext + ' is an old extension that uses a deprecated loading method.' +
					'Please inform the developer that the extension should be updated!');
				legacyExtensionLoading(showdown.extensions[ext], ext);
				return;
				// END LEGACY SUPPORT CODE

			} else if (!showdown.helper.isUndefined(extensions[ext])) {
				ext = extensions[ext];

			} else {
				throw Error('Extension "' + ext + '" could not be loaded. It was either not found or is not a valid extension.');
			}
		}

		if (typeof ext === 'function') {
			ext = ext();
		}

		if (!showdown.helper.isArray(ext)) {
			ext = [ext];
		}

		var validExt = validate(ext, name);
		if (!validExt.valid) {
			throw Error(validExt.error);
		}

		for (var i = 0; i < ext.length; ++i) {
			switch (ext[i].type) {

				case 'lang':
					langExtensions.push(ext[i]);
					break;

				case 'output':
					outputModifiers.push(ext[i]);
					break;
			}
			if (ext[i].hasOwnProperty(listeners)) {
				for (var ln in ext[i].listeners) {
					if (ext[i].listeners.hasOwnProperty(ln)) {
						listen(ln, ext[i].listeners[ln]);
					}
				}
			}
		}

	}

	/**
	 * LEGACY_SUPPORT
	 * @param {*} ext
	 * @param {string} name
	 */
	function legacyExtensionLoading(ext, name) {
		if (typeof ext === 'function') {
			ext = ext(new showdown.Converter());
		}
		if (!showdown.helper.isArray(ext)) {
			ext = [ext];
		}
		var valid = validate(ext, name);

		if (!valid.valid) {
			throw Error(valid.error);
		}

		for (var i = 0; i < ext.length; ++i) {
			switch (ext[i].type) {
				case 'lang':
					langExtensions.push(ext[i]);
					break;
				case 'output':
					outputModifiers.push(ext[i]);
					break;
				default:// should never reach here
					throw Error('Extension loader error: Type unrecognized!!!');
			}
		}
	}

	/**
	 * Listen to an event
	 * @param {string} name
	 * @param {function} callback
	 */
	function listen(name, callback) {
		if (!showdown.helper.isString(name)) {
			throw Error('Invalid argument in converter.listen() method: name must be a string, but ' + typeof name + ' given');
		}

		if (typeof callback !== 'function') {
			throw Error('Invalid argument in converter.listen() method: callback must be a function, but ' + typeof callback + ' given');
		}

		if (!listeners.hasOwnProperty(name)) {
			listeners[name] = [];
		}
		listeners[name].push(callback);
	}

	function rTrimInputText(text) {
		var rsp = text.match(/^\s*/)[0].length,
			rgx = new RegExp('^\\s{0,' + rsp + '}', 'gm');
		return text.replace(rgx, '');
	}

	/**
	 * Dispatch an event
	 * @private
	 * @param {string} evtName Event name
	 * @param {string} text Text
	 * @param {{}} options Converter Options
	 * @param {{}} globals
	 * @returns {string}
	 */
	this._dispatch = function dispatch(evtName, text, options, globals) {
		if (listeners.hasOwnProperty(evtName)) {
			for (var ei = 0; ei < listeners[evtName].length; ++ei) {
				var nText = listeners[evtName][ei](evtName, text, this, options, globals);
				if (nText && typeof nText !== 'undefined') {
					text = nText;
				}
			}
		}
		return text;
	};

	/**
	 * Listen to an event
	 * @param {string} name
	 * @param {function} callback
	 * @returns {showdown.Converter}
	 */
	this.listen = function (name, callback) {
		listen(name, callback);
		return this;
	};

	/**
	 * Converts a markdown string into HTML
	 * @param {string} text
	 * @returns {*}
	 */
	this.makeHtml = function (text) {
		//check if text is not falsy
		if (!text) {
			return text;
		}

		var globals = {
			gHtmlBlocks: [],
			gHtmlMdBlocks: [],
			gHtmlSpans: [],
			gUrls: {},
			gTitles: {},
			gDimensions: {},
			gListLevel: 0,
			hashLinkCounts: {},
			langExtensions: langExtensions,
			outputModifiers: outputModifiers,
			converter: this,
			ghCodeBlocks: []
		};

		// attacklab: Replace ~ with ~T
		// This lets us use tilde as an escape char to avoid md5 hashes
		// The choice of character is arbitrary; anything that isn't
		// magic in Markdown will work.
		text = text.replace(/~/g, '~T');

		// attacklab: Replace $ with ~D
		// RegExp interprets $ as a special character
		// when it's in a replacement string
		text = text.replace(/\$/g, '~D');

		// Standardize line endings
		text = text.replace(/\r\n/g, '\n'); // DOS to Unix
		text = text.replace(/\r/g, '\n'); // Mac to Unix

		if (options.smartIndentationFix) {
			text = rTrimInputText(text);
		}

		// Make sure text begins and ends with a couple of newlines:
		//text = '\n\n' + text + '\n\n';
		text = text;
		// detab
		text = showdown.subParser('detab')(text, options, globals);

		// stripBlankLines
		text = showdown.subParser('stripBlankLines')(text, options, globals);

		//run languageExtensions
		showdown.helper.forEach(langExtensions, function (ext) {
			text = showdown.subParser('runExtension')(ext, text, options, globals);
		});

		// run the sub parsers
		text = showdown.subParser('hashPreCodeTags')(text, options, globals);
		text = showdown.subParser('githubCodeBlocks')(text, options, globals);
		text = showdown.subParser('hashHTMLBlocks')(text, options, globals);
		text = showdown.subParser('hashHTMLSpans')(text, options, globals);
		text = showdown.subParser('stripLinkDefinitions')(text, options, globals);
		text = showdown.subParser('blockGamut')(text, options, globals);
		text = showdown.subParser('unhashHTMLSpans')(text, options, globals);
		text = showdown.subParser('unescapeSpecialChars')(text, options, globals);

		// attacklab: Restore dollar signs
		text = text.replace(/~D/g, '$$');

		// attacklab: Restore tildes
		text = text.replace(/~T/g, '~');

		// Run output modifiers
		showdown.helper.forEach(outputModifiers, function (ext) {
			text = showdown.subParser('runExtension')(ext, text, options, globals);
		});
		return text;
	};

	/**
	 * Set an option of this Converter instance
	 * @param {string} key
	 * @param {*} value
	 */
	this.setOption = function (key, value) {
		options[key] = value;
	};

	/**
	 * Get the option of this Converter instance
	 * @param {string} key
	 * @returns {*}
	 */
	this.getOption = function (key) {
		return options[key];
	};

	/**
	 * Get the options of this Converter instance
	 * @returns {{}}
	 */
	this.getOptions = function () {
		return options;
	};

	/**
	 * Add extension to THIS converter
	 * @param {{}} extension
	 * @param {string} [name=null]
	 */
	this.addExtension = function (extension, name) {
		name = name || null;
		_parseExtension(extension, name);
	};

	/**
	 * Use a global registered extension with THIS converter
	 * @param {string} extensionName Name of the previously registered extension
	 */
	this.useExtension = function (extensionName) {
		_parseExtension(extensionName);
	};

	/**
	 * Set the flavor THIS converter should use
	 * @param {string} name
	 */
	this.setFlavor = function (name) {
		if (flavor.hasOwnProperty(name)) {
			var preset = flavor[name];
			for (var option in preset) {
				if (preset.hasOwnProperty(option)) {
					options[option] = preset[option];
				}
			}
		}
	};

	/**
	 * Remove an extension from THIS converter.
	 * Note: This is a costly operation. It's better to initialize a new converter
	 * and specify the extensions you wish to use
	 * @param {Array} extension
	 */
	this.removeExtension = function (extension) {
		if (!showdown.helper.isArray(extension)) {
			extension = [extension];
		}
		for (var a = 0; a < extension.length; ++a) {
			var ext = extension[a];
			for (var i = 0; i < langExtensions.length; ++i) {
				if (langExtensions[i] === ext) {
					langExtensions[i].splice(i, 1);
				}
			}
			for (var ii = 0; ii < outputModifiers.length; ++i) {
				if (outputModifiers[ii] === ext) {
					outputModifiers[ii].splice(i, 1);
				}
			}
		}
	};

	/**
	 * Get all extension of THIS converter
	 * @returns {{language: Array, output: Array}}
	 */
	this.getAllExtensions = function () {
		return {
			language: langExtensions,
			output: outputModifiers
		};
	};
};

/**
 * Turn Markdown link shortcuts into XHTML <a> tags.
 */
showdown.subParser('anchors', function (text, options, globals) {
	'use strict';

	text = globals.converter._dispatch('anchors.before', text, options, globals);

	var writeAnchorTag = function (wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
		if (showdown.helper.isUndefined(m7)) {
			m7 = '';
		}
		wholeMatch = m1;
		var linkText = m2,
			linkId = m3.toLowerCase(),
			url = m4,
			title = m7;

		if (!url) {
			if (!linkId) {
				// lower-case and turn embedded newlines into spaces
				linkId = linkText.toLowerCase().replace(/ ?\n/g, ' ');
			}
			url = '#' + linkId;

			if (!showdown.helper.isUndefined(globals.gUrls[linkId])) {
				url = globals.gUrls[linkId];
				if (!showdown.helper.isUndefined(globals.gTitles[linkId])) {
					title = globals.gTitles[linkId];
				}
			} else {
				if (wholeMatch.search(/\(\s*\)$/m) > -1) {
					// Special case for explicit empty url
					url = '';
				} else {
					return wholeMatch;
				}
			}
		}

		url = showdown.helper.escapeCharacters(url, '*_', false);
		var result = '<a href="' + url + '"';

		if (title !== '' && title !== null) {
			title = title.replace(/"/g, '&quot;');
			title = showdown.helper.escapeCharacters(title, '*_', false);
			result += ' title="' + title + '"';
		}

		result += '>' + linkText + '</a>';

		return result;
	};

	// First, handle reference-style links: [link text] [id]
	/*
   text = text.replace(/
   (							// wrap whole match in $1
   \[
   (
   (?:
   \[[^\]]*\]		// allow brackets nested one level
   |
   [^\[]			// or anything else
   )*
   )
   \]

   [ ]?					// one optional space
   (?:\n[ ]*)?				// one optional newline followed by spaces

   \[
   (.*?)					// id = $3
   \]
   )()()()()					// pad remaining backreferences
   /g,_DoAnchors_callback);
   */
	text = text.replace(/(\[((?:\[[^\]]*]|[^\[\]])*)][ ]?(?:\n[ ]*)?\[(.*?)])()()()()/g, writeAnchorTag);

	//
	// Next, inline-style links: [link text](url "optional title")
	//

	/*
   text = text.replace(/
   (						// wrap whole match in $1
   \[
   (
   (?:
   \[[^\]]*\]	// allow brackets nested one level
   |
   [^\[\]]			// or anything else
   )
   )
   \]
   \(						// literal paren
   [ \t]*
   ()						// no id, so leave $3 empty
   <?(.*?)>?				// href = $4
   [ \t]*
   (						// $5
   (['"])				// quote char = $6
   (.*?)				// Title = $7
   \6					// matching quote
   [ \t]*				// ignore any spaces/tabs between closing quote and )
   )?						// title is optional
   \)
   )
   /g,writeAnchorTag);
   */
	text = text.replace(/(\[((?:\[[^\]]*]|[^\[\]])*)]\([ \t]*()<?(.*?(?:\(.*?\).*?)?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,
		writeAnchorTag);

	//
	// Last, handle reference-style shortcuts: [link text]
	// These must come last in case you've also got [link test][1]
	// or [link test](/foo)
	//

	/*
   text = text.replace(/
   (                // wrap whole match in $1
   \[
   ([^\[\]]+)       // link text = $2; can't contain '[' or ']'
   \]
   )()()()()()      // pad rest of backreferences
   /g, writeAnchorTag);
   */
	text = text.replace(/(\[([^\[\]]+)])()()()()()/g, writeAnchorTag);

	text = globals.converter._dispatch('anchors.after', text, options, globals);
	return text;
});

showdown.subParser('autoLinks', function (text, options, globals) {
	'use strict';

	text = globals.converter._dispatch('autoLinks.before', text, options, globals);

	var simpleURLRegex = /\b(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+)(?=\s|$)(?!["<>])/gi,
		delimUrlRegex = /<(((https?|ftp|dict):\/\/|www\.)[^'">\s]+)>/gi,
		simpleMailRegex = /(?:^|[ \n\t])([A-Za-z0-9!#$%&'*+-/=?^_`\{|}~\.]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)(?:$|[ \n\t])/gi,
		delimMailRegex = /<(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi;

	text = text.replace(delimUrlRegex, replaceLink);
	text = text.replace(delimMailRegex, replaceMail);
	// simpleURLRegex  = /\b(((https?|ftp|dict):\/\/|www\.)[-.+~:?#@!$&'()*,;=[\]\w]+)\b/gi,
	// Email addresses: <address@domain.foo>

	if (options.simplifiedAutoLink) {
		text = text.replace(simpleURLRegex, replaceLink);
		text = text.replace(simpleMailRegex, replaceMail);
	}

	function replaceLink(wm, link) {
		var lnkTxt = link;
		if (/^www\./i.test(link)) {
			link = link.replace(/^www\./i, 'http://www.');
		}
		return '<a href="' + link + '">' + lnkTxt + '</a>';
	}

	function replaceMail(wholeMatch, m1) {
		var unescapedStr = showdown.subParser('unescapeSpecialChars')(m1);
		return showdown.subParser('encodeEmailAddress')(unescapedStr);
	}

	text = globals.converter._dispatch('autoLinks.after', text, options, globals);

	return text;
});

/**
 * These are all the transformations that form block-level
 * tags like paragraphs, headers, and list items.
 */
showdown.subParser('blockGamut', function (text, options, globals) {
	'use strict';

	text = globals.converter._dispatch('blockGamut.before', text, options, globals);

	// we parse blockquotes first so that we can have headings and hrs
	// inside blockquotes
	text = showdown.subParser('blockQuotes')(text, options, globals);
	text = showdown.subParser('headers')(text, options, globals);

	// Do Horizontal Rules:
	var key = showdown.subParser('hashBlock')('<hr />', options, globals);
	text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, key);
	text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm, key);
	text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, key);

	text = showdown.subParser('lists')(text, options, globals);
	text = showdown.subParser('codeBlocks')(text, options, globals);
	text = showdown.subParser('tables')(text, options, globals);

	// We already ran _HashHTMLBlocks() before, in Markdown(), but that
	// was to escape raw HTML in the original Markdown source. This time,
	// we're escaping the markup we've just created, so that we don't wrap
	// <p> tags around block-level tags.
	text = showdown.subParser('hashHTMLBlocks')(text, options, globals);
	text = showdown.subParser('paragraphs')(text, options, globals);

	text = globals.converter._dispatch('blockGamut.after', text, options, globals);

	return text;
});

showdown.subParser('blockQuotes', function (text, options, globals) {
	'use strict';

	text = globals.converter._dispatch('blockQuotes.before', text, options, globals);
	/*
   text = text.replace(/
   (								// Wrap whole match in $1
   (
   ^[ \t]*>[ \t]?			// '>' at the start of a line
   .+\n					// rest of the first line
   (.+\n)*					// subsequent consecutive lines
   \n*						// blanks
   )+
   )
   /gm, function(){...});
   */

	text = text.replace(/((^[ \t]{0,3}>[ \t]?.+\n(.+\n)*\n*)+)/gm, function (wholeMatch, m1) {
		var bq = m1;

		// attacklab: hack around Konqueror 3.5.4 bug:
		// "----------bug".replace(/^-/g,"") == "bug"
		bq = bq.replace(/^[ \t]*>[ \t]?/gm, '~0'); // trim one level of quoting

		// attacklab: clean up hack
		bq = bq.replace(/~0/g, '');

		bq = bq.replace(/^[ \t]+$/gm, ''); // trim whitespace-only lines
		bq = showdown.subParser('githubCodeBlocks')(bq, options, globals);
		bq = showdown.subParser('blockGamut')(bq, options, globals); // recurse

		bq = bq.replace(/(^|\n)/g, '$1  ');
		// These leading spaces screw with <pre> content, so we need to fix that:
		bq = bq.replace(/(\s*<pre>[^\r]+?<\/pre>)/gm, function (wholeMatch, m1) {
			var pre = m1;
			// attacklab: hack around Konqueror 3.5.4 bug:
			pre = pre.replace(/^  /mg, '~0');
			pre = pre.replace(/~0/g, '');
			return pre;
		});

		return showdown.subParser('hashBlock')('<blockquote>\n' + bq + '\n</blockquote>', options, globals);
	});

	text = globals.converter._dispatch('blockQuotes.after', text, options, globals);
	return text;
});

/**
 * Process Markdown `<pre><code>` blocks.
 */
showdown.subParser('codeBlocks', function (text, options, globals) {
	'use strict';

	text = globals.converter._dispatch('codeBlocks.before', text, options, globals);
	/*
   text = text.replace(text,
   /(?:\n\n|^)
   (								// $1 = the code block -- one or more lines, starting with a space/tab
   (?:
   (?:[ ]{4}|\t)			// Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
   .*\n+
   )+
   )
   (\n*[ ]{0,3}[^ \t\n]|(?=~0))	// attacklab: g_tab_width
   /g,function(){...});
   */

	// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
	text += '~0';

	var pattern = /(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g;
	text = text.replace(pattern, function (wholeMatch, m1, m2) {
		var codeblock = m1,
			nextChar = m2,
			end = '\n';

		codeblock = showdown.subParser('outdent')(codeblock);
		codeblock = showdown.subParser('encodeCode')(codeblock);
		codeblock = showdown.subParser('detab')(codeblock);
		codeblock = codeblock.replace(/^\n+/g, ''); // trim leading newlines
		codeblock = codeblock.replace(/\n+$/g, ''); // trim trailing newlines

		if (options.omitExtraWLInCodeBlocks) {
			end = '';
		}

		codeblock = '<pre><code>' + codeblock + end + '</code></pre>';

		return showdown.subParser('hashBlock')(codeblock, options, globals) + nextChar;
	});

	// attacklab: strip sentinel
	text = text.replace(/~0/, '');

	text = globals.converter._dispatch('codeBlocks.after', text, options, globals);
	return text;
});

/**
 *
 *   *  Backtick quotes are used for <code></code> spans.
 *
 *   *  You can use multiple backticks as the delimiters if you want to
 *     include literal backticks in the code span. So, this input:
 *
 *         Just type ``foo `bar` baz`` at the prompt.
 *
 *       Will translate to:
 *
 *         <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
 *
 *    There's no arbitrary limit to the number of backticks you
 *    can use as delimters. If you need three consecutive backticks
 *    in your code, use four for delimiters, etc.
 *
 *  *  You can use spaces to get literal backticks at the edges:
 *
 *         ... type `` `bar` `` ...
 *
 *       Turns to:
 *
 *         ... type <code>`bar`</code> ...
 */
showdown.subParser('codeSpans', function (text, options, globals) {
	'use strict';

	text = globals.converter._dispatch('codeSpans.before', text, options, globals);

	/*
   text = text.replace(/
   (^|[^\\])					// Character before opening ` can't be a backslash
   (`+)						// $2 = Opening run of `
   (							// $3 = The code block
   [^\r]*?
   [^`]					// attacklab: work around lack of lookbehind
   )
   \2							// Matching closer
   (?!`)
   /gm, function(){...});
   */

	if (typeof (text) === 'undefined') {
		text = '';
	}
	text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
		function (wholeMatch, m1, m2, m3) {
			var c = m3;
			c = c.replace(/^([ \t]*)/g, '');	// leading whitespace
			c = c.replace(/[ \t]*$/g, '');	// trailing whitespace
			c = showdown.subParser('encodeCode')(c);
			return m1 + '<code>' + c + '</code>';
		}
	);

	text = globals.converter._dispatch('codeSpans.after', text, options, globals);
	return text;
});

/**
 * Convert all tabs to spaces
 */
showdown.subParser('detab', function (text) {
	'use strict';

	// expand first n-1 tabs
	text = text.replace(/\t(?=\t)/g, '    '); // g_tab_width

	// replace the nth with two sentinels
	text = text.replace(/\t/g, '~A~B');

	// use the sentinel to anchor our regex so it doesn't explode
	text = text.replace(/~B(.+?)~A/g, function (wholeMatch, m1) {
		var leadingText = m1,
			numSpaces = 4 - leadingText.length % 4;  // g_tab_width

		// there *must* be a better way to do this:
		for (var i = 0; i < numSpaces; i++) {
			leadingText += ' ';
		}

		return leadingText;
	});

	// clean up sentinels
	text = text.replace(/~A/g, '    ');  // g_tab_width
	text = text.replace(/~B/g, '');

	return text;

});

/**
 * Smart processing for ampersands and angle brackets that need to be encoded.
 */
showdown.subParser('encodeAmpsAndAngles', function (text) {
	'use strict';
	// Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
	// http://bumppo.net/projects/amputator/
	text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, '&amp;');

	// Encode naked <'s
	text = text.replace(/<(?![a-z\/?\$!])/gi, '&lt;');

	return text;
});

/**
 * Returns the string, with after processing the following backslash escape sequences.
 *
 * attacklab: The polite way to do this is with the new escapeCharacters() function:
 *
 *    text = escapeCharacters(text,"\\",true);
 *    text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
 *
 * ...but we're sidestepping its use of the (slow) RegExp constructor
 * as an optimization for Firefox.  This function gets called a LOT.
 */
showdown.subParser('encodeBackslashEscapes', function (text) {
	'use strict';
	text = text.replace(/\\(\\)/g, showdown.helper.escapeCharactersCallback);
	text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, showdown.helper.escapeCharactersCallback);
	return text;
});

/**
 * Encode/escape certain characters inside Markdown code runs.
 * The point is that in code, these characters are literals,
 * and lose their special Markdown meanings.
 */
showdown.subParser('encodeCode', function (text) {
	'use strict';

	// Encode all ampersands; HTML entities are not
	// entities within a Markdown code span.
	text = text.replace(/&/g, '&amp;');

	// Do the angle bracket song and dance:
	text = text.replace(/</g, '&lt;');
	text = text.replace(/>/g, '&gt;');

	// Now, escape characters that are magic in Markdown:
	text = showdown.helper.escapeCharacters(text, '*_{}[]\\', false);

	// jj the line above breaks this:
	//---
	//* Item
	//   1. Subitem
	//            special char: *
	// ---

	return text;
});

/**
 *  Input: an email address, e.g. "foo@example.com"
 *
 *  Output: the email address as a mailto link, with each character
 *    of the address encoded as either a decimal or hex entity, in
 *    the hopes of foiling most address harvesting spam bots. E.g.:
 *
 *    <a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;
 *       x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;
 *       &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>
 *
 *  Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
 *  mailing list: <http://tinyurl.com/yu7ue>
 *
 */
showdown.subParser('encodeEmailAddress', function (addr) {
	'use strict';

	var encode = [
		function (ch) {
			return '&#' + ch.charCodeAt(0) + ';';
		},
		function (ch) {
			return '&#x' + ch.charCodeAt(0).toString(16) + ';';
		},
		function (ch) {
			return ch;
		}
	];

	addr = 'mailto:' + addr;

	addr = addr.replace(/./g, function (ch) {
		if (ch === '@') {
			// this *must* be encoded. I insist.
			ch = encode[Math.floor(Math.random() * 2)](ch);
		} else if (ch !== ':') {
			// leave ':' alone (to spot mailto: later)
			var r = Math.random();
			// roughly 10% raw, 45% hex, 45% dec
			ch = (
				r > 0.9 ? encode[2](ch) : r > 0.45 ? encode[1](ch) : encode[0](ch)
			);
		}
		return ch;
	});

	addr = '<a href="' + addr + '">' + addr + '</a>';
	addr = addr.replace(/">.+:/g, '">'); // strip the mailto: from the visible part

	return addr;
});

/**
 * Within tags -- meaning between < and > -- encode [\ ` * _] so they
 * don't conflict with their use in Markdown for code, italics and strong.
 */
showdown.subParser('escapeSpecialCharsWithinTagAttributes', function (text) {
	'use strict';

	// Build a regex to find HTML tags and comments.  See Friedl's
	// "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
	var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;

	text = text.replace(regex, function (wholeMatch) {
		var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, '$1`');
		tag = showdown.helper.escapeCharacters(tag, '\\`*_', false);
		return tag;
	});

	return text;
});

/**
 * Handle github codeblocks prior to running HashHTML so that
 * HTML contained within the codeblock gets escaped properly
 * Example:
 * ```ruby
 *     def hello_world(x)
 *       puts "Hello, #{x}"
 *     end
 * ```
 */
showdown.subParser('githubCodeBlocks', function (text, options, globals) {
	'use strict';

	// early exit if option is not enabled
	if (!options.ghCodeBlocks) {
		return text;
	}

	text = globals.converter._dispatch('githubCodeBlocks.before', text, options, globals);

	text += '~0';

	text = text.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g, function (wholeMatch, language, codeblock) {
		var end = (options.omitExtraWLInCodeBlocks) ? '' : '\n';

		// First parse the github code block
		codeblock = showdown.subParser('encodeCode')(codeblock);
		codeblock = showdown.subParser('detab')(codeblock);
		codeblock = codeblock.replace(/^\n+/g, ''); // trim leading newlines
		codeblock = codeblock.replace(/\n+$/g, ''); // trim trailing whitespace

		codeblock = '<pre><code' + (language ? ' class="' + language + ' language-' + language + '"' : '') + '>' + codeblock + end + '</code></pre>';

		codeblock = showdown.subParser('hashBlock')(codeblock, options, globals);

		// Since GHCodeblocks can be false positives, we need to
		// store the primitive text and the parsed text in a global var,
		// and then return a token
		return '\n\n~G' + (globals.ghCodeBlocks.push({text: wholeMatch, codeblock: codeblock}) - 1) + 'G\n\n';
	});

	// attacklab: strip sentinel
	text = text.replace(/~0/, '');

	return globals.converter._dispatch('githubCodeBlocks.after', text, options, globals);
});

showdown.subParser('hashBlock', function (text, options, globals) {
	'use strict';
	text = text.replace(/(^\n+|\n+$)/g, '');
	return '\n\n~K' + (globals.gHtmlBlocks.push(text) - 1) + 'K\n\n';
});

showdown.subParser('hashElement', function (text, options, globals) {
	'use strict';

	return function (wholeMatch, m1) {
		var blockText = m1;

		// Undo double lines
		blockText = blockText.replace(/\n\n/g, '\n');
		blockText = blockText.replace(/^\n/, '');

		// strip trailing blank lines
		blockText = blockText.replace(/\n+$/g, '');

		// Replace the element text with a marker ("~KxK" where x is its key)
		blockText = '\n\n~K' + (globals.gHtmlBlocks.push(blockText) - 1) + 'K\n\n';

		return blockText;
	};
});

showdown.subParser('hashHTMLBlocks', function (text, options, globals) {
	'use strict';

	var blockTags = [
			'pre',
			'div',
			'h1',
			'h2',
			'h3',
			'h4',
			'h5',
			'h6',
			'blockquote',
			'table',
			'dl',
			'ol',
			'ul',
			'script',
			'noscript',
			'form',
			'fieldset',
			'iframe',
			'math',
			'style',
			'section',
			'header',
			'footer',
			'nav',
			'article',
			'aside',
			'address',
			'audio',
			'canvas',
			'figure',
			'hgroup',
			'output',
			'video',
			'p'
		],
		repFunc = function (wholeMatch, match, left, right) {
			var txt = wholeMatch;
			// check if this html element is marked as markdown
			// if so, it's contents should be parsed as markdown
			if (left.search(/\bmarkdown\b/) !== -1) {
				txt = left + globals.converter.makeHtml(match) + right;
			}
			return '\n\n~K' + (globals.gHtmlBlocks.push(txt) - 1) + 'K\n\n';
		};

	for (var i = 0; i < blockTags.length; ++i) {
		text = showdown.helper.replaceRecursiveRegExp(text, repFunc, '^(?: |\\t){0,3}<' + blockTags[i] + '\\b[^>]*>', '</' + blockTags[i] + '>', 'gim');
	}

	// HR SPECIAL CASE
	text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,
		showdown.subParser('hashElement')(text, options, globals));

	// Special case for standalone HTML comments:
	text = text.replace(/(<!--[\s\S]*?-->)/g,
		showdown.subParser('hashElement')(text, options, globals));

	// PHP and ASP-style processor instructions (<?...?> and <%...%>)
	text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,
		showdown.subParser('hashElement')(text, options, globals));
	return text;
});

/**
 * Hash span elements that should not be parsed as markdown
 */
showdown.subParser('hashHTMLSpans', function (text, config, globals) {
	'use strict';

	var matches = showdown.helper.matchRecursiveRegExp(text, '<code\\b[^>]*>', '</code>', 'gi');

	for (var i = 0; i < matches.length; ++i) {
		text = text.replace(matches[i][0], '~L' + (globals.gHtmlSpans.push(matches[i][0]) - 1) + 'L');
	}
	return text;
});

/**
 * Unhash HTML spans
 */
showdown.subParser('unhashHTMLSpans', function (text, config, globals) {
	'use strict';

	for (var i = 0; i < globals.gHtmlSpans.length; ++i) {
		text = text.replace('~L' + i + 'L', globals.gHtmlSpans[i]);
	}

	return text;
});

/**
 * Hash span elements that should not be parsed as markdown
 */
showdown.subParser('hashPreCodeTags', function (text, config, globals) {
	'use strict';

	var repFunc = function (wholeMatch, match, left, right) {
		// encode html entities
		var codeblock = left + showdown.subParser('encodeCode')(match) + right;
		return '\n\n~G' + (globals.ghCodeBlocks.push({text: wholeMatch, codeblock: codeblock}) - 1) + 'G\n\n';
	};

	text = showdown.helper.replaceRecursiveRegExp(text, repFunc, '^(?: |\\t){0,3}<pre\\b[^>]*>\\s*<code\\b[^>]*>', '^(?: |\\t){0,3}</code>\\s*</pre>', 'gim');
	return text;
});

showdown.subParser('headers', function (text, options, globals) {
	'use strict';

	text = globals.converter._dispatch('headers.before', text, options, globals);

	var prefixHeader = options.prefixHeaderId,
		headerLevelStart = (isNaN(parseInt(options.headerLevelStart))) ? 1 : parseInt(options.headerLevelStart),

		// Set text-style headers:
		//	Header 1
		//	========
		//
		//	Header 2
		//	--------
		//
		setextRegexH1 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n={2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n=+[ \t]*\n+/gm,
		setextRegexH2 = (options.smoothLivePreview) ? /^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm : /^(.+)[ \t]*\n-+[ \t]*\n+/gm;

	text = text.replace(setextRegexH1, function (wholeMatch, m1) {

		var spanGamut = showdown.subParser('spanGamut')(m1, options, globals),
			hID = (options.noHeaderId) ? '' : ' id="' + headerId(m1) + '"',
			hLevel = headerLevelStart,
			hashBlock = '<h' + hLevel + hID + '>' + spanGamut + '</h' + hLevel + '>';
		return showdown.subParser('hashBlock')(hashBlock, options, globals);
	});

	text = text.replace(setextRegexH2, function (matchFound, m1) {
		var spanGamut = showdown.subParser('spanGamut')(m1, options, globals),
			hID = (options.noHeaderId) ? '' : ' id="' + headerId(m1) + '"',
			hLevel = headerLevelStart + 1,
			hashBlock = '<h' + hLevel + hID + '>' + spanGamut + '</h' + hLevel + '>';
		return showdown.subParser('hashBlock')(hashBlock, options, globals);
	});

	// atx-style headers:
	//  # Header 1
	//  ## Header 2
	//  ## Header 2 with closing hashes ##
	//  ...
	//  ###### Header 6
	//
	text = text.replace(/^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+/gm, function (wholeMatch, m1, m2) {
		var span = showdown.subParser('spanGamut')(m2, options, globals),
			hID = (options.noHeaderId) ? '' : ' id="' + headerId(m2) + '"',
			hLevel = headerLevelStart - 1 + m1.length,
			header = '<h' + hLevel + hID + '>' + span + '</h' + hLevel + '>';

		return showdown.subParser('hashBlock')(header, options, globals);
	});

	function headerId(m) {
		var title, escapedId = m.replace(/[^\w]/g, '').toLowerCase();

		if (globals.hashLinkCounts[escapedId]) {
			title = escapedId + '-' + (globals.hashLinkCounts[escapedId]++);
		} else {
			title = escapedId;
			globals.hashLinkCounts[escapedId] = 1;
		}

		// Prefix id to prevent causing inadvertent pre-existing style matches.
		if (prefixHeader === true) {
			prefixHeader = 'section';
		}

		if (showdown.helper.isString(prefixHeader)) {
			return prefixHeader + title;
		}
		return title;
	}

	text = globals.converter._dispatch('headers.after', text, options, globals);
	return text;
});

/**
 * Turn Markdown image shortcuts into <img> tags.
 */
showdown.subParser('images', function (text, options, globals) {
	'use strict';

	text = globals.converter._dispatch('images.before', text, options, globals);

	var inlineRegExp = /!\[(.*?)]\s?\([ \t]*()<?(\S+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(['"])(.*?)\6[ \t]*)?\)/g,
		referenceRegExp = /!\[([^\]]*?)] ?(?:\n *)?\[(.*?)]()()()()()/g;

	function writeImageTag(wholeMatch, altText, linkId, url, width, height, m5, title) {

		var gUrls = globals.gUrls,
			gTitles = globals.gTitles,
			gDims = globals.gDimensions;

		linkId = linkId.toLowerCase();

		if (!title) {
			title = '';
		}

		if (url === '' || url === null) {
			if (linkId === '' || linkId === null) {
				// lower-case and turn embedded newlines into spaces
				linkId = altText.toLowerCase().replace(/ ?\n/g, ' ');
			}
			url = '#' + linkId;

			if (!showdown.helper.isUndefined(gUrls[linkId])) {
				url = gUrls[linkId];
				if (!showdown.helper.isUndefined(gTitles[linkId])) {
					title = gTitles[linkId];
				}
				if (!showdown.helper.isUndefined(gDims[linkId])) {
					width = gDims[linkId].width;
					height = gDims[linkId].height;
				}
			} else {
				return wholeMatch;
			}
		}

		altText = altText.replace(/"/g, '&quot;');
		altText = showdown.helper.escapeCharacters(altText, '*_', false);
		url = showdown.helper.escapeCharacters(url, '*_', false);
		var result = '<img src="' + url + '" alt="' + altText + '"';

		if (title) {
			title = title.replace(/"/g, '&quot;');
			title = showdown.helper.escapeCharacters(title, '*_', false);
			result += ' title="' + title + '"';
		}

		if (width && height) {
			width = (width === '*') ? 'auto' : width;
			height = (height === '*') ? 'auto' : height;

			result += ' width="' + width + '"';
			result += ' height="' + height + '"';
		}

		result += ' />';
		return result;
	}

	// First, handle reference-style labeled images: ![alt text][id]
	text = text.replace(referenceRegExp, writeImageTag);

	// Next, handle inline images:  ![alt text](url =<width>x<height> "optional title")
	text = text.replace(inlineRegExp, writeImageTag);

	text = globals.converter._dispatch('images.after', text, options, globals);
	return text;
});

showdown.subParser('italicsAndBold', function (text, options, globals) {
	'use strict';

	text = globals.converter._dispatch('italicsAndBold.before', text, options, globals);

	if (options.literalMidWordUnderscores) {
		//underscores
		// Since we are consuming a \s character, we need to add it
		text = text.replace(/(^|\s|>|\b)__(?=\S)([\s\S]+?)__(?=\b|<|\s|$)/gm, '$1<strong>$2</strong>');
		text = text.replace(/(^|\s|>|\b)_(?=\S)([\s\S]+?)_(?=\b|<|\s|$)/gm, '$1<em>$2</em>');
		//asterisks
		text = text.replace(/(\*\*)(?=\S)([^\r]*?\S[*]*)\1/g, '<strong>$2</strong>');
		text = text.replace(/(\*)(?=\S)([^\r]*?\S)\1/g, '<em>$2</em>');

	} else {
		// <strong> must go first:
		text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, '<strong>$2</strong>');
		text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, '<em>$2</em>');
	}

	text = globals.converter._dispatch('italicsAndBold.after', text, options, globals);
	return text;
});

/**
 * Form HTML ordered (numbered) and unordered (bulleted) lists.
 */
showdown.subParser('lists', function (text, options, globals) {
	'use strict';

	text = globals.converter._dispatch('lists.before', text, options, globals);

	/**
	 * Process the contents of a single ordered or unordered list, splitting it
	 * into individual list items.
	 * @param {string} listStr
	 * @param {boolean} trimTrailing
	 * @returns {string}
	 */
	function processListItems(listStr, trimTrailing) {
		// The $g_list_level global keeps track of when we're inside a list.
		// Each time we enter a list, we increment it; when we leave a list,
		// we decrement. If it's zero, we're not in a list anymore.
		//
		// We do this because when we're not inside a list, we want to treat
		// something like this:
		//
		//    I recommend upgrading to version
		//    8. Oops, now this line is treated
		//    as a sub-list.
		//
		// As a single paragraph, despite the fact that the second line starts
		// with a digit-period-space sequence.
		//
		// Whereas when we're inside a list (or sub-list), that line will be
		// treated as the start of a sub-list. What a kludge, huh? This is
		// an aspect of Markdown's syntax that's hard to parse perfectly
		// without resorting to mind-reading. Perhaps the solution is to
		// change the syntax rules such that sub-lists must start with a
		// starting cardinal number; e.g. "1." or "a.".
		globals.gListLevel++;

		// trim trailing blank lines:
		listStr = listStr.replace(/\n{2,}$/, '\n');

		// attacklab: add sentinel to emulate \z
		listStr += '~0';

		var rgx = /(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,
			isParagraphed = (/\n[ \t]*\n(?!~0)/.test(listStr));

		listStr = listStr.replace(rgx, function (wholeMatch, m1, m2, m3, m4, taskbtn, checked) {
			checked = (checked && checked.trim() !== '');
			var item = showdown.subParser('outdent')(m4, options, globals),
				bulletStyle = '';

			// Support for github tasklists
			if (taskbtn && options.tasklists) {
				bulletStyle = ' class="task-list-item" style="list-style-type: none;"';
				item = item.replace(/^[ \t]*\[(x|X| )?]/m, function () {
					var otp = '<input type="checkbox" disabled style="margin: 0px 0.35em 0.25em -1.6em; vertical-align: middle;"';
					if (checked) {
						otp += ' checked';
					}
					otp += '>';
					return otp;
				});
			}
			// m1 - Leading line or
			// Has a double return (multi paragraph) or
			// Has sublist
			if (m1 || (item.search(/\n{2,}/) > -1)) {
				item = showdown.subParser('githubCodeBlocks')(item, options, globals);
				item = showdown.subParser('blockGamut')(item, options, globals);
			} else {
				// Recursion for sub-lists:
				item = showdown.subParser('lists')(item, options, globals);
				item = item.replace(/\n$/, ''); // chomp(item)
				if (isParagraphed) {
					item = showdown.subParser('paragraphs')(item, options, globals);
				} else {
					item = showdown.subParser('spanGamut')(item, options, globals);
				}
			}
			item = '\n<li' + bulletStyle + '>' + item + '</li>\n';
			return item;
		});

		// attacklab: strip sentinel
		listStr = listStr.replace(/~0/g, '');

		globals.gListLevel--;

		if (trimTrailing) {
			listStr = listStr.replace(/\s+$/, '');
		}

		return listStr;
	}

	/**
	 * Check and parse consecutive lists (better fix for issue #142)
	 * @param {string} list
	 * @param {string} listType
	 * @param {boolean} trimTrailing
	 * @returns {string}
	 */
	function parseConsecutiveLists(list, listType, trimTrailing) {
		// check if we caught 2 or more consecutive lists by mistake
		// we use the counterRgx, meaning if listType is UL we look for UL and vice versa
		var counterRxg = (listType === 'ul') ? /^ {0,2}\d+\.[ \t]/gm : /^ {0,2}[*+-][ \t]/gm,
			subLists = [],
			result = '';

		if (list.search(counterRxg) !== -1) {
			(function parseCL(txt) {
				var pos = txt.search(counterRxg);
				if (pos !== -1) {
					// slice
					result += '\n\n<' + listType + '>' + processListItems(txt.slice(0, pos), !!trimTrailing) + '</' + listType + '>\n\n';

					// invert counterType and listType
					listType = (listType === 'ul') ? 'ol' : 'ul';
					counterRxg = (listType === 'ul') ? /^ {0,2}\d+\.[ \t]/gm : /^ {0,2}[*+-][ \t]/gm;

					//recurse
					parseCL(txt.slice(pos));
				} else {
					result += '\n\n<' + listType + '>' + processListItems(txt, !!trimTrailing) + '</' + listType + '>\n\n';
				}
			})(list);
			for (var i = 0; i < subLists.length; ++i) {

			}
		} else {
			result = '\n\n<' + listType + '>' + processListItems(list, !!trimTrailing) + '</' + listType + '>\n\n';
		}

		return result;
	}

	// attacklab: add sentinel to hack around khtml/safari bug:
	// http://bugs.webkit.org/show_bug.cgi?id=11231
	text += '~0';

	// Re-usable pattern to match any entire ul or ol list:
	var wholeList = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;

	if (globals.gListLevel) {
		text = text.replace(wholeList, function (wholeMatch, list, m2) {
			var listType = (m2.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
			return parseConsecutiveLists(list, listType, true);
		});
	} else {
		wholeList = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
		//wholeList = /(\n\n|^\n?)( {0,3}([*+-]|\d+\.)[ \t]+[\s\S]+?)(?=(~0)|(\n\n(?!\t| {2,}| {0,3}([*+-]|\d+\.)[ \t])))/g;
		text = text.replace(wholeList, function (wholeMatch, m1, list, m3) {

			var listType = (m3.search(/[*+-]/g) > -1) ? 'ul' : 'ol';
			return parseConsecutiveLists(list, listType);
		});
	}

	// attacklab: strip sentinel
	text = text.replace(/~0/, '');

	text = globals.converter._dispatch('lists.after', text, options, globals);
	return text;
});

/**
 * Remove one level of line-leading tabs or spaces
 */
showdown.subParser('outdent', function (text) {
	'use strict';

	// attacklab: hack around Konqueror 3.5.4 bug:
	// "----------bug".replace(/^-/g,"") == "bug"
	text = text.replace(/^(\t|[ ]{1,4})/gm, '~0'); // attacklab: g_tab_width

	// attacklab: clean up hack
	text = text.replace(/~0/g, '');

	return text;
});

/**
 *
 */
showdown.subParser('paragraphs', function (text, options, globals) {
	'use strict';

	text = globals.converter._dispatch('paragraphs.before', text, options, globals);
	// Strip leading and trailing lines:
	text = text.replace(/^\n+/g, '');
	text = text.replace(/\n+$/g, '');

	var grafs = text.split(/\n{2,}/g),
		grafsOut = [],
		end = grafs.length; // Wrap <p> tags

	for (var i = 0; i < end; i++) {
		var str = grafs[i];
		// if this is an HTML marker, copy it
		if (str.search(/~(K|G)(\d+)\1/g) >= 0) {
			grafsOut.push(str);
		} else {
			str = showdown.subParser('spanGamut')(str, options, globals);
			str = str.replace(/^([ \t]*)/g, '<p>');
			str += '</p>';
			grafsOut.push(str);
		}
	}

	/** Unhashify HTML blocks */
	end = grafsOut.length;
	for (i = 0; i < end; i++) {
		var blockText = '',
			grafsOutIt = grafsOut[i],
			codeFlag = false;
		// if this is a marker for an html block...
		while (grafsOutIt.search(/~(K|G)(\d+)\1/) >= 0) {
			var delim = RegExp.$1,
				num = RegExp.$2;

			if (delim === 'K') {
				blockText = globals.gHtmlBlocks[num];
			} else {
				// we need to check if ghBlock is a false positive
				if (codeFlag) {
					// use encoded version of all text
					blockText = showdown.subParser('encodeCode')(globals.ghCodeBlocks[num].text);
				} else {
					blockText = globals.ghCodeBlocks[num].codeblock;
				}
			}
			blockText = blockText.replace(/\$/g, '$$$$'); // Escape any dollar signs

			grafsOutIt = grafsOutIt.replace(/(\n\n)?~(K|G)\d+\2(\n\n)?/, blockText);
			// Check if grafsOutIt is a pre->code
			if (/^<pre\b[^>]*>\s*<code\b[^>]*>/.test(grafsOutIt)) {
				codeFlag = true;
			}
		}
		grafsOut[i] = grafsOutIt;
	}
	text = grafsOut.join('\n\n');
	// Strip leading and trailing lines:
	text = text.replace(/^\n+/g, '');
	text = text.replace(/\n+$/g, '');
	return globals.converter._dispatch('paragraphs.after', text, options, globals);
});

/**
 * Run extension
 */
showdown.subParser('runExtension', function (ext, text, options, globals) {
	'use strict';

	if (ext.filter) {
		text = ext.filter(text, globals.converter, options);

	} else if (ext.regex) {
		// TODO remove this when old extension loading mechanism is deprecated
		var re = ext.regex;
		if (!re instanceof RegExp) {
			re = new RegExp(re, 'g');
		}
		text = text.replace(re, ext.replace);
	}

	return text;
});

/**
 * These are all the transformations that occur *within* block-level
 * tags like paragraphs, headers, and list items.
 */
showdown.subParser('spanGamut', function (text, options, globals) {
	'use strict';

	text = globals.converter._dispatch('spanGamut.before', text, options, globals);
	text = showdown.subParser('codeSpans')(text, options, globals);
	text = showdown.subParser('escapeSpecialCharsWithinTagAttributes')(text, options, globals);
	text = showdown.subParser('encodeBackslashEscapes')(text, options, globals);

	// Process anchor and image tags. Images must come first,
	// because ![foo][f] looks like an anchor.
	text = showdown.subParser('images')(text, options, globals);
	text = showdown.subParser('anchors')(text, options, globals);

	// Make links out of things like `<http://example.com/>`
	// Must come after _DoAnchors(), because you can use < and >
	// delimiters in inline links like [this](<url>).
	text = showdown.subParser('autoLinks')(text, options, globals);
	text = showdown.subParser('encodeAmpsAndAngles')(text, options, globals);
	text = showdown.subParser('italicsAndBold')(text, options, globals);
	text = showdown.subParser('strikethrough')(text, options, globals);

	// Do hard breaks:
	text = text.replace(/  +\n/g, ' <br />\n');

	text = globals.converter._dispatch('spanGamut.after', text, options, globals);
	return text;
});

showdown.subParser('strikethrough', function (text, options, globals) {
	'use strict';

	if (options.strikethrough) {
		text = globals.converter._dispatch('strikethrough.before', text, options, globals);
		text = text.replace(/(?:~T){2}([\s\S]+?)(?:~T){2}/g, '<del>$1</del>');
		text = globals.converter._dispatch('strikethrough.after', text, options, globals);
	}

	return text;
});

/**
 * Strip any lines consisting only of spaces and tabs.
 * This makes subsequent regexs easier to write, because we can
 * match consecutive blank lines with /\n+/ instead of something
 * contorted like /[ \t]*\n+/
 */
showdown.subParser('stripBlankLines', function (text) {
	'use strict';
	return text.replace(/^[ \t]+$/mg, '');
});

/**
 * Strips link definitions from text, stores the URLs and titles in
 * hash references.
 * Link defs are in the form: ^[id]: url "optional title"
 *
 * ^[ ]{0,3}\[(.+)\]: // id = $1  attacklab: g_tab_width - 1
 * [ \t]*
 * \n?                  // maybe *one* newline
 * [ \t]*
 * <?(\S+?)>?          // url = $2
 * [ \t]*
 * \n?                // maybe one newline
 * [ \t]*
 * (?:
 * (\n*)              // any lines skipped = $3 attacklab: lookbehind removed
 * ["(]
 * (.+?)              // title = $4
 * [")]
 * [ \t]*
 * )?                 // title is optional
 * (?:\n+|$)
 * /gm,
 * function(){...});
 *
 */
showdown.subParser('stripLinkDefinitions', function (text, options, globals) {
	'use strict';

	var regex = /^ {0,3}\[(.+)]:[ \t]*\n?[ \t]*<?(\S+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n+|(?=~0))/gm;

	// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
	text += '~0';

	text = text.replace(regex, function (wholeMatch, linkId, url, width, height, blankLines, title) {
		linkId = linkId.toLowerCase();
		globals.gUrls[linkId] = showdown.subParser('encodeAmpsAndAngles')(url);  // Link IDs are case-insensitive

		if (blankLines) {
			// Oops, found blank lines, so it's not a title.
			// Put back the parenthetical statement we stole.
			return blankLines + title;

		} else {
			if (title) {
				globals.gTitles[linkId] = title.replace(/"|'/g, '&quot;');
			}
			if (options.parseImgDimensions && width && height) {
				globals.gDimensions[linkId] = {
					width: width,
					height: height
				};
			}
		}
		// Completely remove the definition from the text
		return '';
	});

	// attacklab: strip sentinel
	text = text.replace(/~0/, '');

	return text;
});

showdown.subParser('tables', function (text, options, globals) {
	'use strict';

	if (!options.tables) {
		return text;
	}

	var tableRgx = /^[ \t]{0,3}\|?.+\|.+\n[ \t]{0,3}\|?[ \t]*:?[ \t]*(?:-|=){2,}[ \t]*:?[ \t]*\|[ \t]*:?[ \t]*(?:-|=){2,}[\s\S]+?(?:\n\n|~0)/gm;

	function parseStyles(sLine) {
		if (/^:[ \t]*--*$/.test(sLine)) {
			return ' style="text-align:left;"';
		} else if (/^--*[ \t]*:[ \t]*$/.test(sLine)) {
			return ' style="text-align:right;"';
		} else if (/^:[ \t]*--*[ \t]*:$/.test(sLine)) {
			return ' style="text-align:center;"';
		} else {
			return '';
		}
	}

	function parseHeaders(header, style) {
		var id = '';
		header = header.trim();
		if (options.tableHeaderId) {
			id = ' id="' + header.replace(/ /g, '_').toLowerCase() + '"';
		}
		header = showdown.subParser('spanGamut')(header, options, globals);

		return '<th' + id + style + '>' + header + '</th>\n';
	}

	function parseCells(cell, style) {
		var subText = showdown.subParser('spanGamut')(cell, options, globals);
		return '<td' + style + '>' + subText + '</td>\n';
	}

	function buildTable(headers, cells) {
		var tb = '<table>\n<thead>\n<tr>\n',
			tblLgn = headers.length;

		for (var i = 0; i < tblLgn; ++i) {
			tb += headers[i];
		}
		tb += '</tr>\n</thead>\n<tbody>\n';

		for (i = 0; i < cells.length; ++i) {
			tb += '<tr>\n';
			for (var ii = 0; ii < tblLgn; ++ii) {
				tb += cells[i][ii];
			}
			tb += '</tr>\n';
		}
		tb += '</tbody>\n</table>\n';
		return tb;
	}

	text = globals.converter._dispatch('tables.before', text, options, globals);

	text = text.replace(tableRgx, function (rawTable) {

		var i, tableLines = rawTable.split('\n');

		// strip wrong first and last column if wrapped tables are used
		for (i = 0; i < tableLines.length; ++i) {
			if (/^[ \t]{0,3}\|/.test(tableLines[i])) {
				tableLines[i] = tableLines[i].replace(/^[ \t]{0,3}\|/, '');
			}
			if (/\|[ \t]*$/.test(tableLines[i])) {
				tableLines[i] = tableLines[i].replace(/\|[ \t]*$/, '');
			}
		}

		var rawHeaders = tableLines[0].split('|').map(function (s) {
				return s.trim();
			}),
			rawStyles = tableLines[1].split('|').map(function (s) {
				return s.trim();
			}),
			rawCells = [],
			headers = [],
			styles = [],
			cells = [];

		tableLines.shift();
		tableLines.shift();

		for (i = 0; i < tableLines.length; ++i) {
			if (tableLines[i].trim() === '') {
				continue;
			}
			rawCells.push(
				tableLines[i]
					.split('|')
					.map(function (s) {
						return s.trim();
					})
			);
		}

		if (rawHeaders.length < rawStyles.length) {
			return rawTable;
		}

		for (i = 0; i < rawStyles.length; ++i) {
			styles.push(parseStyles(rawStyles[i]));
		}

		for (i = 0; i < rawHeaders.length; ++i) {
			if (showdown.helper.isUndefined(styles[i])) {
				styles[i] = '';
			}
			headers.push(parseHeaders(rawHeaders[i], styles[i]));
		}

		for (i = 0; i < rawCells.length; ++i) {
			var row = [];
			for (var ii = 0; ii < headers.length; ++ii) {
				if (showdown.helper.isUndefined(rawCells[i][ii])) {

				}
				row.push(parseCells(rawCells[i][ii], styles[ii]));
			}
			cells.push(row);
		}

		return buildTable(headers, cells);
	});

	text = globals.converter._dispatch('tables.after', text, options, globals);

	return text;
});

/**
 * Swap back in all the special characters we've hidden.
 */
showdown.subParser('unescapeSpecialChars', function (text) {
	'use strict';

	text = text.replace(/~E(\d+)E/g, function (wholeMatch, m1) {
		var charCodeToReplace = parseInt(m1);
		return String.fromCharCode(charCodeToReplace);
	});
	return text;
});
module.exports = showdown;
